// EditorTextDatabase.cs
//
// Copyright (c) 2009 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;

using Supremacy.Annotations;
using Supremacy.Collections;
using Supremacy.Resources;
using Supremacy.Text;

namespace SupremacyEditor.Text
{
    [Serializable]
    public sealed class EditorTextDatabase : ITextDatabase
    {
        private readonly string _filePath;
        private readonly EditorTextDatabaseTable<ITechObjectTextDatabaseEntry> _techObjectTextTable;
        private readonly EditorTextDatabaseTable<IRaceTextDatabaseEntry> _raceTextTable;

        private EditorTextDatabase([NotNull] string filePath)
        {
            if (filePath == null)
                throw new ArgumentNullException("filePath");
            _filePath = filePath;
            _techObjectTextTable = new EditorTextDatabaseTable<ITechObjectTextDatabaseEntry>();
            _raceTextTable = new EditorTextDatabaseTable<IRaceTextDatabaseEntry>();
        }

        public static EditorTextDatabase Load(string filePath)
        {
            var physicalFilePath = ResourceManager.GetResourcePath(filePath);

            var doc = XDocument.Load(physicalFilePath);
            var database = new EditorTextDatabase(physicalFilePath);

            var techObjectTable = database.TechObjectTextTable;
            var techObjectEntryType = typeof(ITechObjectTextDatabaseEntry).FullName;
            var techObjectTableElement = doc.Root.Elements("Tables")
                .Elements("Table")
                .Where(e => string.Equals((string)e.Attribute("EntryType"), techObjectEntryType))
                .FirstOrDefault();

            if (techObjectTableElement == null)
                return database;

            var techObjectEntries = techObjectTableElement
                .Elements("Entries")
                .Elements("Entry");

            foreach (var entryElement in techObjectEntries)
            {
                var key = (string)entryElement.Attribute("Key");

                if (key == null)
                    continue;

                var entry = new EditorTextDatabaseEntry<ITechObjectTextDatabaseEntry>(key);
                var localizedEntries = entryElement.Elements("LocalizedEntries").Elements("LocalizedEntry");

                foreach (var localizedEntryElement in localizedEntries)
                {
                    var localizedEntry = new TechObjectTextDatabaseEntry(
                        (string)localizedEntryElement.Attribute("Language"),
                        (string)localizedEntryElement.Element("Name"),
                        (string)localizedEntryElement.Element("Description"),
                        (string)localizedEntryElement.Element("Custom1"),
                        (string)localizedEntryElement.Element("Custom2"));

                    entry.AddInternal(localizedEntry);
                }

                techObjectTable.AddInternal(entry);
            }

            var raceTable = database.RaceTextTable;
            var raceEntryType = typeof(IRaceTextDatabaseEntry).FullName;
            var raceTableElement = doc.Root.Elements("Tables")
                .Elements("Table")
                .Where(e => string.Equals((string)e.Attribute("EntryType"), raceEntryType))
                .FirstOrDefault();

            if (raceTableElement == null)
                return database;

            var raceEntries = raceTableElement
                .Elements("Entries")
                .Elements("Entry");

            foreach (var entryElement in raceEntries)
            {
                var key = (string)entryElement.Attribute("Key");

                if (key == null)
                    continue;

                var entry = new EditorTextDatabaseEntry<IRaceTextDatabaseEntry>(key);
                var localizedEntries = entryElement.Elements("LocalizedEntries").Elements("LocalizedEntry");

                foreach (var localizedEntryElement in localizedEntries)
                {
                    var localizedEntry = new RaceTextDatabaseEntry(
                        (string)localizedEntryElement.Attribute("Language"),
                        (string)localizedEntryElement.Element("SingularName"),
                        (string)localizedEntryElement.Element("PluralName"),
                        (string)localizedEntryElement.Element("Description"));

                    entry.AddInternal(localizedEntry);
                }

                raceTable.AddInternal(entry);
            }

            return database;
        }

        public void AcceptChanges()
        {
            _techObjectTextTable.AcceptChanges();
            _raceTextTable.AcceptChanges();
        }

        public bool IsChanged
        {
            get { return _techObjectTextTable.IsChanged || _raceTextTable.IsChanged; }
        }

        public void RejectChanges()
        {
            _techObjectTextTable.RejectChanges();
            _raceTextTable.RejectChanges();
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        ITextDatabaseTable<TEntry> ITextDatabase.GetTable<TEntry>()
        {
            if (typeof(TEntry) == typeof(ITechObjectTextDatabaseEntry))
                return (ITextDatabaseTable<TEntry>)_techObjectTextTable;
            if (typeof(TEntry) == typeof(IRaceTextDatabaseEntry))
                return (ITextDatabaseTable<TEntry>)_raceTextTable;
            return null;
        }

        private EditorTextDatabaseTable<ITechObjectTextDatabaseEntry> TechObjectTextTable
        {
            get { return _techObjectTextTable; }
        }

        private EditorTextDatabaseTable<IRaceTextDatabaseEntry> RaceTextTable
        {
            get { return _raceTextTable; }
        }

        public void Save()
        {
            var doc = new XDocument(
                new XElement(
                    "TextDatabase",
                    new XElement(
                        "Tables",
                        new XElement(
                            "Table",
                            new XAttribute(
                                "EntryType",
                                typeof(ITechObjectTextDatabaseEntry).FullName),
                            new XElement(
                                "Entries",
                                _techObjectTextTable.Select(
                                    o =>
                                    new XElement(
                                        "Entry",
                                        new XAttribute(
                                            "Key",
                                            o.Key),
                                        new XElement(
                                            "LocalizedEntries",
                                            o.LocalizedEntries.Select(
                                                e =>
                                                new XElement(
                                                    "LocalizedEntry",
                                                    new XAttribute(
                                                        "Language",
                                                        e.Language),
                                                    e.GetSavedValues().Select(
                                                        v => new XElement(
                                                            v.Key,
                                                            new XCData(string.Format(CultureInfo.InvariantCulture, "{0}", v.Value).Trim()))))
                                                )
                                            )
                                        )
                                    )
                                )
                            ),
                        new XElement(
                            "Table",
                            new XAttribute(
                                "EntryType",
                                typeof(IRaceTextDatabaseEntry).FullName),
                            new XElement(
                                "Entries",
                                _raceTextTable.Select(
                                    o =>
                                    new XElement(
                                        "Entry",
                                        new XAttribute(
                                            "Key",
                                            o.Key),
                                        new XElement(
                                            "LocalizedEntries",
                                            o.LocalizedEntries.Select(
                                                e =>
                                                new XElement(
                                                    "LocalizedEntry",
                                                    new XAttribute(
                                                        "Language",
                                                        e.Language),
                                                    e.GetSavedValues().Select(
                                                        v => new XElement(
                                                            v.Key,
                                                            new XCData(string.Format(CultureInfo.InvariantCulture, "{0}", v.Value).Trim())))))
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                );
            doc.Save(_filePath);
        }

        #region Nested Type: TechObjectTextDatabaseTable
        [Serializable]
        private class EditorTextDatabaseTable<TLocalizedEntry> : ITextDatabaseTable<TLocalizedEntry> where TLocalizedEntry : class, ILocalizedTextDatabaseEntry
        {
            private readonly KeyedCollectionBase<string, ITextDatabaseEntry<TLocalizedEntry>> _entries;
            private readonly HashSet<ITextDatabaseEntry<TLocalizedEntry>> _newEntries;
            private readonly HashSet<ITextDatabaseEntry<TLocalizedEntry>> _changedEntries;
            private readonly HashSet<ITextDatabaseEntry<TLocalizedEntry>> _removedEntries;

            private bool _isEditInProgress;

            public EditorTextDatabaseTable()
            {
                _entries = new KeyedCollectionBase<string, ITextDatabaseEntry<TLocalizedEntry>>(entry => entry.Key);
                _entries.CollectionChanged += OnEntriesChanged;
                _newEntries = new HashSet<ITextDatabaseEntry<TLocalizedEntry>>();
                _changedEntries = new HashSet<ITextDatabaseEntry<TLocalizedEntry>>();
                _removedEntries = new HashSet<ITextDatabaseEntry<TLocalizedEntry>>();
            }

            public IEnumerator<ITextDatabaseEntry<TLocalizedEntry>> GetEnumerator()
            {
                return _entries.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            internal void AddInternal(ITextDatabaseEntry<TLocalizedEntry> item)
            {
                _entries.Add(item);
            }

            public void Add(ITextDatabaseEntry<TLocalizedEntry> item)
            {
                if (item == null)
                    throw new ArgumentNullException("item");
                AddInternal(item);
                _newEntries.Add(item);
            }

            public void Clear()
            {
                _removedEntries.AddRange(_entries);
            }

            public bool Contains(ITextDatabaseEntry<TLocalizedEntry> item)
            {
                return _entries.Contains(item);
            }

            public void CopyTo(ITextDatabaseEntry<TLocalizedEntry>[] array, int arrayIndex)
            {
                _entries.CopyTo(array, arrayIndex);
            }

            public bool Remove(ITextDatabaseEntry<TLocalizedEntry> item)
            {
                if (_entries.Contains(item))
                    return false;
                _removedEntries.Add(item);
                return true;
            }

            public int Count
            {
                get { return _entries.Count; }
            }

            public bool IsReadOnly
            {
                get { return false; }
            }

            public void AcceptChanges()
            {
                foreach (var changedEntry in _changedEntries)
                    changedEntry.AcceptChanges();
                _changedEntries.Clear();
                _newEntries.Clear();
                _removedEntries.Clear();
            }

            public bool IsChanged
            {
                get { return _newEntries.Any() || _removedEntries.Any() || _changedEntries.Any(); }
            }

            public void RejectChanges()
            {
                foreach (var changedEntry in _changedEntries)
                    changedEntry.RejectChanges();
                _entries.RemoveRange(_newEntries);
                _entries.AddRange(_removedEntries);
                _changedEntries.Clear();
                _newEntries.Clear();
                _removedEntries.Clear();
            }

            public ITextDatabaseEntry<TLocalizedEntry> this[string key]
            {
                get { return _entries[key]; }
            }

            public ITextDatabaseEntry<TLocalizedEntry> CreateEntry(string key)
            {
                var newEntry = new EditorTextDatabaseEntry<TLocalizedEntry>(key);
                Add(newEntry);
                return newEntry;
            }

            public bool Contains(string key)
            {
                return _entries.Contains(key);
            }

            public bool Remove(string key)
            {
                ITextDatabaseEntry<TLocalizedEntry> value;
                if (!_entries.TryGetValue(key, out value))
                    return false;
                _removedEntries.Add(value);
                return true;
            }

            public bool TryGetEntry(string key, out ITextDatabaseEntry<TLocalizedEntry> entry)
            {
                return _entries.TryGetValue(key, out entry);
            }

            public event NotifyCollectionChangedEventHandler CollectionChanged;

            private void OnEntriesChanged(object sender, NotifyCollectionChangedEventArgs args)
            {
                var newItems = args.NewItems;
                if (newItems != null)
                {
                    foreach (var newItem in newItems.OfType<ITextDatabaseEntry<TLocalizedEntry>>())
                        newItem.CollectionChanged += OnEntryCollectionChanged;
                }

                var oldItems = args.OldItems;
                if (oldItems != null)
                {
                    foreach (var oldItem in oldItems.OfType<ITextDatabaseEntry<TLocalizedEntry>>())
                        oldItem.CollectionChanged -= OnEntryCollectionChanged;
                }

                var handler = this.CollectionChanged;
                if (handler != null)
                    handler(this, args);
            }

            private void OnEntryCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (!_isEditInProgress)
                    return;
                _changedEntries.Add((ITextDatabaseEntry<TLocalizedEntry>)sender);
            }

            public void BeginEdit()
            {
                _isEditInProgress = true;
            }

            public void EndEdit()
            {
                AcceptChanges();
                _isEditInProgress = false;
            }

            public void CancelEdit()
            {
                RejectChanges();
                _isEditInProgress = false;
            }
        }
        #endregion

        #region Nested Type: EditorTextDatabaseEntry
        [Serializable]
        private class EditorTextDatabaseEntry<TLocalizedEntry> : ITextDatabaseEntry<TLocalizedEntry>
            where TLocalizedEntry : class, ILocalizedTextDatabaseEntry
        {
            private readonly string _key;
            private readonly KeyedCollectionBase<string, TLocalizedEntry> _localizedEntries;
            private readonly HashSet<TLocalizedEntry> _newEntries;
            private readonly HashSet<TLocalizedEntry> _changedEntries;
            private readonly HashSet<TLocalizedEntry> _removedEntries;

            private bool _isEditInProgress;

            public EditorTextDatabaseEntry([NotNull] string key)
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                _key = key;
                _localizedEntries = new KeyedCollectionBase<string, TLocalizedEntry>(entry => entry.Language);
                _localizedEntries.CollectionChanged += OnLocalizedEntriesCollectionChanged;
                _newEntries = new HashSet<TLocalizedEntry>();
                _changedEntries = new HashSet<TLocalizedEntry>();
                _removedEntries = new HashSet<TLocalizedEntry>();
            }

            private void OnLocalizedEntriesCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
            {
                var newItems = args.NewItems;
                if (newItems != null)
                {
                    foreach (var newItem in newItems.OfType<TLocalizedEntry>())
                        newItem.Changed += OnLocalizedEntryChanged;
                }

                var oldItems = args.OldItems;
                if (oldItems != null)
                {
                    foreach (var oldItem in oldItems.OfType<TLocalizedEntry>())
                        oldItem.Changed -= OnLocalizedEntryChanged;
                }

                var handler = this.CollectionChanged;
                if (handler != null)
                    handler(this, args);
            }

            private void OnLocalizedEntryChanged(object sender, EventArgs e)
            {
                if (!_isEditInProgress)
                    return;
                _changedEntries.Add((TLocalizedEntry)sender);
                OnChanged();
            }

            public IEnumerator<TLocalizedEntry> GetEnumerator()
            {
                return _localizedEntries.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            public void Add([NotNull] TLocalizedEntry item)
            {
                if (item == null)
                    throw new ArgumentNullException("item");
                item.Changed += OnLocalizedEntryChanged;
                _localizedEntries.Add(item);
                _newEntries.Add(item);
            }

            private void OnChanged()
            {
                var handler = this.Changed;
                if (handler != null)
                    handler(this, EventArgs.Empty);
            }

            public void Clear()
            {
                var localizedEntries = _localizedEntries.ToList();
                _removedEntries.AddRange(localizedEntries);
                _localizedEntries.RemoveRange(localizedEntries);
            }

            public bool Contains(TLocalizedEntry item)
            {
                return _localizedEntries.Contains(item);
            }

            public void CopyTo(TLocalizedEntry[] array, int arrayIndex)
            {
                _localizedEntries.CopyTo(array, arrayIndex);
            }

            public bool Remove(TLocalizedEntry item)
            {
                if (!_localizedEntries.Remove(item))
                    return false;
                _removedEntries.Add(item);
                return true;
            }

            public int Count
            {
                get { return _localizedEntries.Count; }
            }

            public bool IsReadOnly
            {
                get { return false; }
            }

            public void AcceptChanges()
            {
                foreach (var changedEntry in _changedEntries)
                    changedEntry.AcceptChanges();
                _changedEntries.Clear();
                _newEntries.Clear();
                _removedEntries.Clear();
                _isEditInProgress = false;
            }

            public bool IsChanged
            {
                get { return _newEntries.Any() || _removedEntries.Any() || _changedEntries.Any(); }
            }

            public void RejectChanges()
            {
                foreach (var changedEntry in _changedEntries)
                    changedEntry.RejectChanges();
                _localizedEntries.RemoveRange(_newEntries);
                _localizedEntries.AddRange(_removedEntries);
                _changedEntries.Clear();
                _removedEntries.Clear();
                _newEntries.Clear();
            }

            public string Key
            {
                get { return _key; }
            }

            public IEnumerable<TLocalizedEntry> LocalizedEntries
            {
                get { return _localizedEntries; }
            }

            public TLocalizedEntry CreateLocalizedEntry(string language)
            {
                Type type = null;
                if (typeof(TLocalizedEntry) == typeof(ITechObjectTextDatabaseEntry))
                    type = typeof(TechObjectTextDatabaseEntry);
                else if (typeof(TLocalizedEntry) == typeof(IRaceTextDatabaseEntry))
                    type = typeof(RaceTextDatabaseEntry);
                if (type == null)
                    throw new ArgumentException("Unsupported type: " + typeof(TLocalizedEntry).FullName);
                var newEntry = (TLocalizedEntry)Activator.CreateInstance(type, new object[] { language });
                Add(newEntry);
                return newEntry;
            }

            public void AddInternal(TLocalizedEntry entry)
            {
                _localizedEntries.Add(entry);
            }

            public TLocalizedEntry GetLocalizedEntry(string language)
            {
                var culture = ResourceManager.CurrentCulture;
                TLocalizedEntry localizedEntry;

                if (_localizedEntries.TryGetValue(culture.Name, out localizedEntry))
                    return localizedEntry;

                while (!culture.IsNeutralCulture && (culture.Parent != null) && (culture.Parent != culture))
                {
                    culture = culture.Parent;
                    if (_localizedEntries.TryGetValue(culture.Name, out localizedEntry))
                        return localizedEntry;
                }

                culture = ResourceManager.NeutralCulture;

                if (_localizedEntries.TryGetValue(culture.Name, out localizedEntry))
                    return localizedEntry;

                while (!culture.IsNeutralCulture && (culture.Parent != null) && (culture.Parent != culture))
                {
                    culture = culture.Parent;
                    if (_localizedEntries.TryGetValue(culture.Name, out localizedEntry))
                        return localizedEntry;
                }

                return null;
            }

            public event NotifyCollectionChangedEventHandler CollectionChanged;
            
            public void BeginEdit()
            {
                _isEditInProgress = true;
            }

            public void EndEdit()
            {
                AcceptChanges();
                _isEditInProgress = false;
            }

            public void CancelEdit()
            {
                RejectChanges();
                _isEditInProgress = false;
            }

            public event EventHandler<EventArgs> Changed;
        }
        #endregion

        #region Nested Type: TechObjectTextDatabaseEntry
        [Serializable]
        private class TechObjectTextDatabaseEntry : EditorItemBase<ITechObjectTextDatabaseEntry>, ITechObjectTextDatabaseEntry
        {
            private readonly string _language;

            // ReSharper disable MemberCanBePrivate.Local
            public TechObjectTextDatabaseEntry([NotNull] string language)
                : base(new TechObjectTextDatabaseEntryBase(language))
            {
                if (language == null)
                    throw new ArgumentNullException("language");

                _language = language;

                RegisterAccessor(() => this.Name, o => o.Name, (o, v) => o.Name = v);
                RegisterAccessor(() => this.Description, o => o.Description, (o, v) => o.Description = v);
                RegisterAccessor(() => this.Custom1, o => o.Custom1, (o, v) => o.Custom1 = v);
                RegisterAccessor(() => this.Custom2, o => o.Custom2, (o, v) => o.Custom2 = v);
            }
            // ReSharper restore MemberCanBePrivate.Local

            public TechObjectTextDatabaseEntry(
                [NotNull] string language,
                string name,
                string description,
                string custom1,
                string custom2)
                : this(language)
            {
                if (name != null)
                    name = name.Trim();
                if (description != null)
                    description = description.Trim();
                if (custom1 != null)
                    custom1 = custom1.Trim();
                if (custom2 != null)
                    custom2 = custom2.Trim();

                this.Name = name;
                this.Description = description;
                this.Custom1 = custom1;
                this.Custom2 = custom2;
            }

            public override string Header
            {
                get { return this.Name; }
            }

            public string Language
            {
                get { return _language; }
            }

            public string LanguageName
            {
                get { return CultureInfo.GetCultureInfo(this.Language).EnglishName; }
            }

            public Dictionary<string, object> GetSavedValues()
            {
                return new Dictionary<string, object>
                       {
                           { "Name", this.BaseItem.Name },
                           { "Description", this.BaseItem.Description },
                           { "Custom1", this.BaseItem.Custom1 },
                           { "Custom2", this.BaseItem.Custom2 }
                       };
            }

            public string Name
            {
                get { return GetValue(() => this.Name); }
                set { SetValue(() => this.Name, value); }
            }

            public string Description
            {
                get { return GetValue(() => this.Description); }
                set { SetValue(() => this.Description, value); }
            }

            public string Custom1
            {
                get { return GetValue(() => this.Custom1); }
                set { SetValue(() => this.Custom1, value); }
            }

            public string Custom2
            {
                get { return GetValue(() => this.Custom2); }
                set { SetValue(() => this.Custom2, value); }
            }

            private class TechObjectTextDatabaseEntryBase : ITechObjectTextDatabaseEntry
            {
                private readonly string _language;

                public TechObjectTextDatabaseEntryBase([NotNull] string language)
                {
                    if (language == null)
                        throw new ArgumentNullException("language");
                    _language = language;
                }

                // ReSharper disable UnusedAutoPropertyAccessor.Local

                #region IRevertibleChangeTracking Members
                public void AcceptChanges() { throw new NotSupportedException(); }
                public bool IsChanged { get { return false; } }
                public void RejectChanges() { throw new NotSupportedException(); }
                #endregion

                #region IEditableObject Members
                public void BeginEdit() {}
                public void EndEdit() {}
                public void CancelEdit() {}
                #endregion

                #region ILocalizedTextDatabaseEntry Members
                public string Language
                {
                    get { return _language; }
                }

                public string LanguageName
                {
                    get { return CultureInfo.GetCultureInfo(this.Language).EnglishName; }
                }

                public Dictionary<string, object> GetSavedValues()
                {
                    return new Dictionary<string, object>
                           {
                               { "Name", this.Name },
                               { "Description", this.Description },
                               { "Custom1", this.Custom1 },
                               { "Custom2", this.Custom2 }
                           };
                }
                #endregion

                #region ITechObjectTextDatabaseEntry Members
                public string Name { get; set; }
                public string Description { get; set; }
                public string Custom1 { get; set; }
                public string Custom2 { get; set; }
                #endregion

                // ReSharper restore UnusedAutoPropertyAccessor.Local
                public event EventHandler<EventArgs> Changed;
            }
        }
        #endregion

        #region Nested Type: RaceTextDatabaseEntry
        [Serializable]
        private class RaceTextDatabaseEntry : EditorItemBase<IRaceTextDatabaseEntry>, IRaceTextDatabaseEntry
        {
            private readonly string _language;

            // ReSharper disable MemberCanBePrivate.Local
            public RaceTextDatabaseEntry([NotNull] string language)
                : base(new RaceTextDatabaseEntryBase(language))
            {
                if (language == null)
                    throw new ArgumentNullException("language");

                _language = language;

                RegisterAccessor(() => this.SingularName, o => o.SingularName, (o, v) => o.SingularName = v);
                RegisterAccessor(() => this.PluralName, o => o.PluralName, (o, v) => o.PluralName = v);
                RegisterAccessor(() => this.Description, o => o.Description, (o, v) => o.Description = v);
            }
            // ReSharper restore MemberCanBePrivate.Local

            public RaceTextDatabaseEntry(
                [NotNull] string language,
                string singularName,
                string pluralName,
                string description)
                : this(language)
            {
                if (singularName != null)
                    singularName = singularName.Trim();
                if (pluralName != null)
                    pluralName = pluralName.Trim();
                if (description != null)
                    description = description.Trim();

                this.SingularName = singularName;
                this.PluralName = pluralName;
                this.Description = description;
            }

            public override string Header
            {
                get { return this.PluralName; }
            }

            public string Language
            {
                get { return _language; }
            }

            public string LanguageName
            {
                get { return CultureInfo.GetCultureInfo(this.Language).EnglishName; }
            }

            public Dictionary<string, object> GetSavedValues()
            {
                return new Dictionary<string, object>
                       {
                           { "SingularName", base.BaseItem.SingularName },
                           { "PluralName", base.BaseItem.PluralName },
                           { "Description", base.BaseItem.Description }
                       };
            }

            public string SingularName
            {
                get { return GetValue(() => this.SingularName); }
                set { SetValue(() => this.SingularName, value); }
            }

            public string PluralName
            {
                get { return GetValue(() => this.PluralName); }
                set { SetValue(() => this.PluralName, value); }
            }

            public string Description
            {
                get { return GetValue(() => this.Description); }
                set { SetValue(() => this.Description, value); }
            }

            private class RaceTextDatabaseEntryBase : IRaceTextDatabaseEntry
            {
                private readonly string _language;

                public RaceTextDatabaseEntryBase([NotNull] string language)
                {
                    if (language == null)
                        throw new ArgumentNullException("language");
                    _language = language;
                }

                // ReSharper disable UnusedAutoPropertyAccessor.Local

                #region IRevertibleChangeTracking Members
                public void AcceptChanges() { throw new NotSupportedException(); }
                public bool IsChanged { get { return false; } }
                public void RejectChanges() { throw new NotSupportedException(); }
                #endregion

                #region IEditableObject Members
                public void BeginEdit() { }
                public void EndEdit() { }
                public void CancelEdit() { }
                #endregion

                #region ILocalizedTextDatabaseEntry Members
                public string Language
                {
                    get { return _language; }
                }

                public string LanguageName
                {
                    get { return CultureInfo.GetCultureInfo(this.Language).EnglishName; }
                }

                public Dictionary<string, object> GetSavedValues()
                {
                    return new Dictionary<string, object>
                       {
                           { "SingularName", this.SingularName },
                           { "PluralName", this.PluralName },
                           { "Description", this.Description }
                       };
                }
                #endregion

                #region IRaceTextDatabaseEntry Members
                public string SingularName { get; set; }
                public string PluralName { get; set; }
                public string Description { get; set; }
                #endregion

                // ReSharper restore UnusedAutoPropertyAccessor.Local
                public event EventHandler<EventArgs> Changed;
            }
        }
        #endregion
    }
}